﻿using ILRuntime.CLR.TypeSystem;
using ILRuntime.Runtime.Intepreter;
using System;
using System.Collections;
using System.IO;
using System.Linq;
using System.Reflection;
using UnityEngine;

namespace Smart.Model
{
    public class HotfixManager
    {
        static HotfixManager ms_handle = new HotfixManager();
        const string HOTFIX_DLL_NAME = "Hotfix.dll";
        public static HotfixManager Instance
        {
            get
            {
                return ms_handle;
            }
        }

        public IEnumerator LoadAssemblies(string url,string bundleName,System.Action onFailed)
        {
            byte[] assBytes = null;
            byte[] pdbBytes = null;
#if UNITY_EDITOR
            assBytes = System.IO.File.ReadAllBytes($"{Application.dataPath}/../Library/ScriptAssemblies/Hotfix.dll");
            yield return null;
            pdbBytes = System.IO.File.ReadAllBytes($"{Application.dataPath}/../Library/ScriptAssemblies/Hotfix.pdb");
            yield return null;
#else
            var ResourceComponent = Entry.Instance.ResourceComponent;
            yield return ResourceComponent.LoadAssetBundle(url, bundleName, ab =>
             {
                 var code = ab.LoadAsset<UnityEngine.GameObject>("Code");
                 var codeScript = code.GetComponent<Code>();
                 assBytes = codeScript.code_dll.bytes;
                 pdbBytes = codeScript.code_pdb.bytes;
                 Logger.Log($"<color=#00ff00>[代码加载成功]:[code:{assBytes.Length}bytes]:[pdb:{pdbBytes.Length}bytes]</color>");
             }, null);
#endif

            if (null == assBytes || assBytes.Length <= 0)
            {
                onFailed?.Invoke();
                yield break;
            }

            if (null == pdbBytes || pdbBytes.Length <= 0)
            {
                onFailed?.Invoke();
                yield break;
            }

#if ILRuntime
            Model.Logger.Log($"<color=#00ff00>[游戏模式]:ILRuntime</color>");
			this.appDomain = new ILRuntime.Runtime.Enviorment.AppDomain();
			this.dllStream = new MemoryStream(assBytes);
			this.pdbStream = new MemoryStream(pdbBytes);
			this.appDomain.LoadAssembly(dllStream, pdbStream, new ILRuntime.Mono.Cecil.Pdb.PdbReaderProvider());

#if DEBUG && (UNITY_EDITOR || UNITY_ANDROID || UNITY_IPHONE)
			//由于Unity的Profiler接口只允许在主线程使用，为了避免出异常，需要告诉ILRuntime主线程的线程ID才能正确将函数运行耗时报告给Profiler
			this.appDomain.UnityMainThreadID = System.Threading.Thread.CurrentThread.ManagedThreadId;
#endif
			ILHelper.InitILRuntime(this.appDomain);
            this.hotfixTypes = this.appDomain.LoadedTypes.Values.Select(x => x.ReflectionType).ToArray();

			this.awake = new ILStaticMethod(this.appDomain, HotfixEntryName, "Awake", 0);
			this.start = new ILStaticMethod(this.appDomain, HotfixEntryName, "Start", 0);
            this.update = new ILStaticMethod(this.appDomain, HotfixEntryName, "Update", 0);
            this.lateUpdate = new ILStaticMethod(this.appDomain, HotfixEntryName, "LateUpdate", 0);
            this.destroy = new ILStaticMethod(this.appDomain, HotfixEntryName, "Destroy", 0);
#else
            Logger.Log($"<color=#00ff00>[游戏模式]:Mono</color>");
            this.assembly = Assembly.Load(assBytes, pdbBytes);
            this.hotfixEntry = this.assembly.GetType(HotfixEntryName);
            this.hotfixTypes = this.assembly.GetTypes().ToArray();

            this.awake = new MonoStaticMethod(hotfixEntry, "Awake");
            this.start = new MonoStaticMethod(hotfixEntry, "Start");
            this.update = new MonoStaticMethod(hotfixEntry, "Update");
            this.lateUpdate = new MonoStaticMethod(hotfixEntry, "LateUpdate");
            this.destroy = new MonoStaticMethod(hotfixEntry, "Destroy");
#endif
        }

        public IStaticMethod GetStaticMethod(string typeName,string entryName,int argc)
        {
            IStaticMethod method;
#if ILRuntime
            method = new ILStaticMethod(appDomain, typeName, entryName, argc);
#else
            method = new MonoStaticMethod(this.assembly.GetType(typeName), entryName);
#endif
            return method;
        }

        const string HotfixEntryName = "Smart.Hotfix.HotfixEntry";
#if ILRuntime
		private ILRuntime.Runtime.Enviorment.AppDomain appDomain;
		private MemoryStream dllStream;
		private MemoryStream pdbStream;
#else
        private Assembly assembly;
        private Type hotfixEntry;
#endif
        private IStaticMethod awake;
        private IStaticMethod start;
        private IStaticMethod update;
        private IStaticMethod lateUpdate;
        private IStaticMethod destroy;
        private Type[] hotfixTypes;

        Type GetReflectionType(string fullName)
        {
            Type type = null;
#if ILRuntime
			if (fullName.StartsWith("Smart.Hotfix"))
			{
				if (appDomain.LoadedTypes.ContainsKey(fullName))
				{
					IType itype = appDomain.LoadedTypes[fullName];
					type = itype.ReflectionType;
				}
			}
			else
			{
				type = typeof(Smart.Model.GameApplication).Assembly.GetType(fullName);
			}
#else
            type = this.assembly.GetType(fullName);
#endif
            return type;
        }

        public object CreateInstance(string typename)
        {
            if (!typename.StartsWith("Smart.Hotfix"))
            {
                Logger.LogError("typename must startswith Smart.Hotfix");
                return null;
            }

#if ILRuntime
			if (!this.appDomain.LoadedTypes.ContainsKey(typename))
			{
				Model.Logger.LogError($"Fetch ILType For {typename} failed");
				return null;
			}

			var it = this.appDomain.LoadedTypes[typename];
			var type = it.ReflectionType;
			var instance = this.appDomain.Instantiate(type.FullName);
#else
            var instance = this.assembly.CreateInstance(typename);
#endif
            if (null == instance)
            {
                Logger.LogError($"CreateInstance For {typename} Failed");
            }

            return instance;
        }

        public object CreateInstance(Type type)
        {
            if (null == type)
            {
                Logger.LogError($"CreateInstance failed type is null");
                return null;
            }

            var realType = GetReflectionType(type.FullName);
            if (null == realType)
            {
                Logger.LogError($"CreateInstance failed for {type.FullName}");
                return null;
            }

            object instance = null;
#if ILRuntime
			if (realType.FullName.StartsWith("Smart.Hotfix"))
				instance = this.appDomain.Instantiate(realType.FullName);
			else
				instance = realType.Assembly.CreateInstance(realType.FullName);
#else
            instance = realType.Assembly.CreateInstance(realType.FullName);
#endif
            if (null == instance)
            {
                Logger.LogError($"CreateInstance failed for {type.FullName}");
            }

            return instance;
        }

#region Hotfix 生命周期
        public void Awake()
        {
            this.awake?.Run();
        }

        public void Start()
        {
            this.start?.Run();
        }

        public bool Ready
        {
            get;set;
        }

        public void Update()
        {
            this.update?.Run();
        }

        public void LateUpdate()
        {
            this.lateUpdate?.Run();
        }

        public void Destroy()
        {
            this.destroy?.Run();
        }
#endregion

        public Type[] GetHotfixTypes()
        {
            return this.hotfixTypes;
        }

        public void ApplicationQuit()
        {
            this.hotfixTypes = null;
#if ILRuntime
			if (null != this.dllStream)
			{
				this.dllStream.Close();
				this.dllStream = null;
			}

			if (null != this.pdbStream)
			{
				this.pdbStream.Close();
				this.pdbStream = null;
			}
#endif
        }
    }
}